//
//  Copyright (C) 1998-2007 J. Andrew McLaughlin
// 
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by the Free
//  Software Foundation; either version 2 of the License, or (at your option)
//  any later version.
// 
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
//  for more details.
//  
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  kernelMultitasker.c
//

// This file contains the C functions belonging to the kernel's 
// multitasker

#include "kernelMultitasker.h"
#include "kernelDebug.h"
#include "kernelEnvironment.h"
#include "kernelError.h"
#include "kernelFile.h"
#include "kernelInterrupt.h"
#include "kernelLog.h"
#include "kernelMain.h"
#include "kernelMalloc.h"
#include "kernelMemory.h"
#include "kernelMisc.h"
#include "kernelNetwork.h"
#include "kernelPage.h"
#include "kernelParameters.h"
#include "kernelPerformanceCounter.h"
#include "kernelPic.h"
#include "kernelProcessorLevel.h"
#include "kernelProcessorX86.h"
#include "kernelRandom.h"
#include "kernelShutdown.h"
#include "kernelSysTimer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#define PROC_KILLABLE(proc) ((proc != kernelProc) &&        \
                             (proc != exceptionProc) &&     \
                             (proc != idleProc) &&          \
                             (proc != kernelCurrentProcess))

#define SET_PORT_BIT(bitmap, port) \
do { bitmap[port / 8] |=  (1 << (port % 8)); } while (0)
#define UNSET_PORT_BIT(bitmap, port) \
do { bitmap[port / 8] &= ~(1 << (port % 8)); } while (0)
#define GET_PORT_BIT(bitmap, port) ((bitmap[port / 8] >> (port % 8)) & 0x01)
  
// Global multitasker stuff
static int multitaskingEnabled = 0;
static volatile int processIdCounter = KERNELPROCID;
static kernelProcess *kernelProc = NULL;
static kernelProcess *idleProc = NULL;
static kernelProcess *exceptionProc = NULL;
static volatile int processingException = 0;
static volatile unsigned exceptionAddress = 0;
static kernelProcess *fpuProcess = NULL;
static volatile int inScheduler = 0;

// We allow the pointer to the current process to be exported, so that
// when a process uses system calls, there is an easy way for the
// process to get information about itself.
kernelProcess *kernelCurrentProcess = NULL;

// Process queue for CPU execution
static kernelProcess *processQueue[MAX_PROCESSES];
static volatile int numQueued = 0;

// Things specific to the scheduler.  The scheduler process is just a
// convenient place to keep things, we don't use all of it and it doesn't
// go in the queue
static kernelProcess *schedulerProc = NULL;
static volatile int schedulerStop = 0;
static volatile unsigned schedulerTimeslices = 0;
static volatile unsigned schedulerTime = 0;

static int pcDispatchCalls = -1;
static int pcDispatchYieldCalls = -1;
static int pcDispatchAsyncCalls = -1;
static int pcDispatchRescheduleCalls = -1;
static int pcContextSwitches = -1;
static int pcWaitCalls = -1;
static int pcBlockCalls = -1;
static int pcProcessesCreated = -1;
static int pcFpuExceptions = -1;
static int pcFpuContextSaved = -1;
static int pcFpuContextRestored = -1;

// An array of exception types.  The selectors are initialized later.
static struct {
  int index;
  kernelSelector tssSelector;
  const char *a;
  const char *name;
  int (*handler) (void);

} exceptionVector[19] = {
  { EXCEPTION_DIVBYZERO, 0, "a", "divide-by-zero", NULL },
  { EXCEPTION_DEBUG, 0, "a", "debug", NULL },
  { EXCEPTION_NMI, 0, "a", "non-maskable interrupt (NMI)", NULL },
  { EXCEPTION_BREAK, 0, "a", "breakpoint", NULL },
  { EXCEPTION_OVERFLOW, 0, "a", "overflow", NULL },
  { EXCEPTION_BOUNDS, 0, "a", "out-of-bounds", NULL },
  { EXCEPTION_OPCODE, 0, "an", "invalid opcode", NULL },
  { EXCEPTION_DEVNOTAVAIL, 0, "a", "device not available", NULL },
  { EXCEPTION_DOUBLEFAULT, 0, "a", "double-fault", NULL },
  { EXCEPTION_COPROCOVER, 0, "a", "co-processor segment overrun", NULL },
  { EXCEPTION_INVALIDTSS, 0, "an", "invalid TSS", NULL },
  { EXCEPTION_SEGNOTPRES, 0, "a", "segment not present", NULL },
  { EXCEPTION_STACK, 0, "a", "stack", NULL },
  { EXCEPTION_GENPROTECT, 0, "a", "general protection", NULL },
  { EXCEPTION_PAGE, 0, "a", "page fault", NULL },
  { EXCEPTION_RESERVED, 0, "a", "\"reserved\"", NULL },
  { EXCEPTION_FLOAT, 0, "a", "floating point", NULL },
  { EXCEPTION_ALIGNCHECK, 0, "an", "alignment check", NULL },
  { EXCEPTION_MACHCHECK, 0, "a", "machine check", NULL }
};

/****************************************************************************/

#if 1
#define ACQUIRE_LOCK_PROCESS_QUEUE() int interrupts1 = 0; kernelProcessorSuspendInts(interrupts1);
#define RELEASE_LOCK_PROCESS_QUEUE() kernelProcessorRestoreInts(interrupts1);
#else
#define ACQUIRE_LOCK_PROCESS_QUEUE()
#define RELEASE_LOCK_PROCESS_QUEUE()
#endif


static kernelProcess *getProcessById(int processId)
{
	// This routine is used to find a process' pointer based on the process
	// Id.  Nothing fancy -- it just searches through the list.  Maybe later
	// it can be some kind of fancy sorting/searching procedure.  Returns NULL
	// if the process doesn't exist

	kernelProcess *theProcess = NULL;
	int count;

	ACQUIRE_LOCK_PROCESS_QUEUE();

	for (count = 0; count < numQueued; count ++)
	{
		if (!processQueue[count])
			continue;
		if (processQueue[count]->processId == processId)
		{
			theProcess = processQueue[count];
			break;
		}
	}

	RELEASE_LOCK_PROCESS_QUEUE();

	// If we didn't find it, this will still be NULL
	return (theProcess);
}

static kernelProcess *getProcessByName(const char *name)
{
	// As above, but searches by name

	kernelProcess *theProcess = NULL;
	int count;

	ACQUIRE_LOCK_PROCESS_QUEUE();

	for (count = 0; count < numQueued; count ++)
	{
		if (!processQueue[count])
			continue;
		if (!strcmp((char *) processQueue[count]->processName, name))
		{
			theProcess = processQueue[count];
			break;
		}
	}

	RELEASE_LOCK_PROCESS_QUEUE();

	// If we didn't find it, this will still be NULL
	return (theProcess);
}

/****************************************************************************/

static inline int allocProcess(kernelProcess **processPointer)
{
	// This routine is used to allocate new process control memory.  It
	// should be passed a reference to a pointer that will point to
	// the new process, if allocated successfully.

	int status = 0;
	kernelProcess *newProcess = NULL;

	// Make sure the pointer->pointer parameter we were passed isn't NULL
	if (processPointer == NULL)
		return (status = ERR_NULLPARAMETER);
  
	newProcess = kernelMalloc(sizeof(kernelProcess));
	if (newProcess == NULL)
		return (status = ERR_MEMORY);

	// Success.  Set the pointer for the calling process
	*processPointer = newProcess;
	return (status = 0);
}


static inline int freeProcess(kernelProcess *killProcess)
{
	// This routine is used to free process control memory.  It
	// should be passed the process Id of the process to kill.  It
	// returns 0 on success, negative otherwise

	int status = 0;

	status = kernelFree((void *) killProcess);

	return (status);
}


static int addProcessToQueue(kernelProcess *targetProcess)
{
  // This routine will add a process to the task queue.  It returns zero 
  // on success, negative otherwise

  int status = 0;
  int count;
  
  if (targetProcess == NULL)
    // The process does not exist, or is not accessible
    return (status = ERR_NOSUCHPROCESS);

  // Make sure the priority is a legal value
  if ((targetProcess->priority < 0) || 
      (targetProcess->priority > (PRIORITY_LEVELS - 1)))
    // The process' priority is an illegal value
    return (status = ERR_INVALID);

  // Search the process queue to make sure it isn't already present
  for (count = 0; count < numQueued; count ++)
    if (processQueue[count] == targetProcess)
      // Oops, it's already there
      return (status = ERR_ALREADY);
  
  // OK, now we can add the process to the queue
  processQueue[numQueued++] = targetProcess;

  // Done
  return (status = 0);
}


static int removeProcessFromQueue(kernelProcess *targetProcess)
{
  // This routine will remove a process from the task queue.  It returns zero 
  // on success, negative otherwise

  int status = 0;
  int processPosition = 0;
  int count;
  
  if (targetProcess == NULL)
    // The process does not exist, or is not accessible
    return (status = ERR_NOSUCHPROCESS);

  // Search the queue for the matching process
  for (count = 0; count < numQueued; count ++)
    if (processQueue[count] == targetProcess)
      {
	processPosition = count;
	break;
      }

  // Make sure we found the process
  if (processQueue[processPosition] != targetProcess)
    // The process is not in the task queue
    return (status = ERR_NOSUCHPROCESS);

  // Subtract one from the number of queued processes
  numQueued -= 1;

  // OK, now we can remove the process from the queue.  If there are one
  // or more remaining processes in this queue, we will shorten the queue
  // by moving the LAST process into the spot we're vacating
  if ((numQueued > 0) && (processPosition != numQueued))
    processQueue[processPosition] = processQueue[numQueued];

  // Done
  return (status = 0);
}


static int createTaskStateSegment(kernelProcess *theProcess)
{
  // This function will create a TSS (Task State Segment) for a new
  // process based on the attributes of the process.  This function relies
  // on the privilege, userStackSize, and superStackSize attributes having 
  // been previously set.  Returns 0 on success, negative on error.

  int status = 0;

  // Get a free descriptor for the process' TSS
  status = kernelDescriptorRequest(&(theProcess->tssSelector));
  if ((status < 0) || (theProcess->tssSelector == 0))
    // Crap.  An error getting a free descriptor.
    return (status);

  // Fill in the process' Task State Segment descriptor
  status = kernelDescriptorSet(
     theProcess->tssSelector, // TSS selector number
     &(theProcess->taskStateSegment), // Starts at...
     sizeof(kernelTSS),      // Limit of a TSS segment
     1,                      // Present in memory
     PRIVILEGE_SUPERVISOR,   // TSSs are supervisor privilege level
     0,                      // TSSs are system segs
     0xB,                    // TSS, 32-bit, busy
     0,                      // 0 for SMALL size granularity
     0);                     // Must be 0 in TSS
  if (status < 0)
    {
      // Crap.  An error getting a free descriptor.
      kernelDescriptorRelease(theProcess->tssSelector);
      return (status);
    }

  // Now, fill in the TSS (Task State Segment) for the new process.  Parts
  // of this will be different depending on whether this is a user
  // or supervisor mode process

  kernelMemClear((void *) &(theProcess->taskStateSegment), sizeof(kernelTSS));

  // Set the IO bitmap's offset
  theProcess->taskStateSegment.IOMapBase = IOBITMAP_OFFSET;

  if (theProcess->processorPrivilege == PRIVILEGE_SUPERVISOR)
    {
      theProcess->taskStateSegment.CS = PRIV_CODE;
      theProcess->taskStateSegment.DS = PRIV_DATA;
      theProcess->taskStateSegment.SS = PRIV_STACK;
    }
  else
    {
      theProcess->taskStateSegment.CS = USER_CODE;
      theProcess->taskStateSegment.DS = USER_DATA;
      theProcess->taskStateSegment.SS = USER_STACK;

      // Turn off access to all I/O ports by default
      kernelMemSet((void *) theProcess->taskStateSegment.IOMap, 0xFF,
		   PORTS_BYTES);
    }

  theProcess->taskStateSegment.ES = theProcess->taskStateSegment.DS;
  theProcess->taskStateSegment.FS = theProcess->taskStateSegment.DS;
  theProcess->taskStateSegment.GS = theProcess->taskStateSegment.DS;

  theProcess->taskStateSegment.ESP =
    ((unsigned) theProcess->userStack +
     (theProcess->userStackSize - sizeof(int)));

  if (theProcess->processorPrivilege != PRIVILEGE_SUPERVISOR)
    {
      theProcess->taskStateSegment.SS0 = PRIV_STACK;
      theProcess->taskStateSegment.ESP0 = ((unsigned) theProcess->superStack +
					   (theProcess->superStackSize -
					    sizeof(int)));
    }

  theProcess->taskStateSegment.EFLAGS = 0x00000202; // Interrupts enabled
  theProcess->taskStateSegment.CR3 = (unsigned)
    theProcess->pageDirectory->physical;

  // All remaining values will be NULL from initialization.  Note that this 
  // includes the EIP.

  // Return success
  return (status = 0);
}


static int createNewProcess(const char *name, int priority, int privilege,
			    processImage *execImage, int newPageDir)
{
  // This function is used to create a new process in the process
  // queue.  It makes a "defaults" kind of process -- it sets up all of
  // the process' attributes with default values.  If the calling process
  // wants something different, it should reset those attributes afterward.
  // If successful, it returns the processId of the new process.  Otherwise, 
  // it returns negative.

  int status = 0;
  kernelProcess *newProcess = NULL;
  void *stackMemoryAddr = NULL;
  void *physicalCodeData = NULL;
  int *args = NULL;
  char **argv = NULL;
  int argSpaceSize = 0;
  char *argSpace = NULL;
  char *newArgAddress = NULL;
  int length = 0;
  int count;

  // Don't bother checking the parameters, as the external functions 
  // should have done this already.

  // We need to see if we can get some fresh process control memory
  status = allocProcess(&newProcess);
  if (status < 0)
    return (status);

  if (newProcess == NULL)
    {
      kernelError(kernel_error, "New process structure is NULL");
      return (status = ERR_NOFREE);
    }

  // Ok, we got a new, fresh process.  We need to start filling in some
  // of the process' data (after initializing it, of course)
  kernelMemClear((void *) newProcess, sizeof(kernelProcess));

  // Fill in the process' Id number
  newProcess->processId = processIdCounter++;

  // By default, the type is process
  newProcess->type = proc_normal;

  // Now, if the process Id is KERNELPROCID, then we are creating the
  // kernel process, and it will be its own parent.  Otherwise, get the
  // current process and make IT be the parent of this new process
  if (newProcess->processId == KERNELPROCID)
    {
      newProcess->parentProcessId = newProcess->processId;
      newProcess->userId = 1;   // Admin
      // Give it "/" as current working directory
      strncpy((char *) newProcess->currentDirectory, "/", 2);
    }
  else
    {
      // Make sure the current process isn't NULL
      if (kernelCurrentProcess == NULL)
	{
	  kernelError(kernel_error, "No current process!");
	  freeProcess(newProcess);
	  return (status = ERR_NOSUCHPROCESS);
	}

      // Fill in the process' parent Id number
      newProcess->parentProcessId = kernelCurrentProcess->processId;
      // Fill in the process' user Id number
      newProcess->userId = kernelCurrentProcess->userId;
      // Fill in the current working directory
      strncpy((char *) newProcess->currentDirectory, 
	      (char *) kernelCurrentProcess->currentDirectory,
	      MAX_PATH_LENGTH);
      newProcess->currentDirectory[MAX_PATH_LENGTH - 1] = '\0';
    }

  // Fill in the process name
  strncpy((char *) newProcess->processName, name, MAX_PROCNAME_LENGTH);
  newProcess->processName[MAX_PROCNAME_LENGTH - 1] = '\0';

  // Fill in the process' priority level
  newProcess->priority = priority;

  // Fill in the process' privilege level
  newProcess->privilege = privilege;

  // Fill in the process' processor privilege level.  The kernel and its
  // threads get PRIVILEGE_SUPERVISOR, all others get PRIVILEGE_USER.
  if (execImage->virtualAddress >= (void *) KERNEL_VIRTUAL_ADDRESS)
    newProcess->processorPrivilege = PRIVILEGE_SUPERVISOR;
  else
    newProcess->processorPrivilege = PRIVILEGE_USER;
    
  // The amount of time since started (now)
  newProcess->startTime = kernelSysTimerRead();

  // The thread's initial state will be "stopped"
  newProcess->state = proc_stopped;

  // Add the process to the process queue so we can continue whilst doing
  // things like changing memory ownerships
  status = addProcessToQueue(newProcess);
  if (status < 0)
    {
      // Not able to queue the process.
      freeProcess(newProcess);
      return (status);
    }

  // Do we need to create a new page directory and a set of page tables for 
  // this process?
  if (newPageDir)
    {
      if (!execImage->virtualAddress || !execImage->code || 
	  !execImage->codeSize || !execImage->data || !execImage->dataSize ||
	  !execImage->imageSize)
	{
	  kernelError(kernel_error, "New process \"%s\" executable image is "
		      "missing data", name);
	  removeProcessFromQueue(newProcess);
	  freeProcess(newProcess);
	  return (status = ERR_NODATA);
	}

      // We need to make a new page directory, etc.
      newProcess->pageDirectory =
	kernelPageNewDirectory(newProcess->processId);
      if (newProcess->pageDirectory == NULL)
	{
	  // Not able to setup a page directory
	  removeProcessFromQueue(newProcess);
	  freeProcess(newProcess);
	  return (status = ERR_NOVIRTUAL);
	}

      // Get the physical address of the code/data
      physicalCodeData =
	kernelPageGetPhysical(newProcess->parentProcessId, execImage->code);

      // Make the process own its code/data memory.  Don't remap it yet
      // because we want to map it at the requested virtual address.
      status = kernelMemoryChangeOwner(newProcess->parentProcessId,
				newProcess->processId, 0, execImage->code,
				NULL);
      if (status < 0)
	{
	  // Couldn't make the process own its memory
	  removeProcessFromQueue(newProcess);
	  freeProcess(newProcess);
	  return (status);
	}

      // Remap the code/data to the requested virtual address.
      status = kernelPageMap(newProcess->processId, physicalCodeData,
			     execImage->virtualAddress, execImage->imageSize);
      if (status < 0)
	{
	  // Couldn't map the process memory
	  removeProcessFromQueue(newProcess);
	  freeProcess(newProcess);
	  return (status);
	}

      // Code should be read-only
      status =
	kernelPageSetAttrs(newProcess->processId, 0, PAGEFLAG_WRITABLE,
			   execImage->virtualAddress, execImage->codeSize);
      if (status < 0)
	{
	  removeProcessFromQueue(newProcess);
	  freeProcess(newProcess);
	  return (status);
	}
    }
  else
    {
      // This process will share a page directory with its parent
      newProcess->pageDirectory =
	kernelPageShareDirectory(newProcess->parentProcessId,
				 newProcess->processId);
      if (newProcess->pageDirectory == NULL)
	{
	  removeProcessFromQueue(newProcess);
	  freeProcess(newProcess);
	  return (status = ERR_NOVIRTUAL);
	}
    }

  // Give the process a stack
  newProcess->userStackSize = DEFAULT_STACK_SIZE;
  if (newProcess->processorPrivilege != PRIVILEGE_SUPERVISOR)
    newProcess->superStackSize = DEFAULT_SUPER_STACK_SIZE;

  stackMemoryAddr =
    kernelMemoryGet((newProcess->userStackSize + newProcess->superStackSize),
		    "process stack");
  if (stackMemoryAddr == NULL)
    {
      // We couldn't make a stack for the new process.  Maybe the system
      // doesn't have anough available memory?
      removeProcessFromQueue(newProcess);
      freeProcess(newProcess);
      return (status = ERR_MEMORY);
    }

  // Copy 'argc' and 'argv' arguments to the new process' stack while we
  // still own the stack memory.

  // Set pointers to the appropriate stack locations for the arguments
  args = (stackMemoryAddr + newProcess->userStackSize - (2 * sizeof(int)));

  // Calculate the amount of memory we need to allocate for argument data.
  // Leave space for pointers to the strings, since the (int argc,
  // char *argv[]) scheme means just 2 values on the stack: an integer
  // an a pointer to an array of char* pointers...
  argSpaceSize = ((execImage->argc + 1) * sizeof(char *));
  for (count = 0; count < execImage->argc; count ++)
    argSpaceSize += (strlen(execImage->argv[count]) + 1);

  // Get memory for the argument data
  argSpace = kernelMemoryGet(argSpaceSize, "process arguments");
  if (argSpace == NULL)
    {
      kernelMemoryRelease(stackMemoryAddr);
      removeProcessFromQueue(newProcess);
      freeProcess(newProcess);
      return (status = ERR_MEMORY);
    }
      
  // Change ownership to the new process, and share it back with this process.
  if (kernelMemoryChangeOwner(newProcess->parentProcessId,
			      newProcess->processId, 1, argSpace,
			      (void **) &newArgAddress) < 0)
    {
      kernelMemoryRelease(stackMemoryAddr);
      kernelMemoryRelease(argSpace);
      removeProcessFromQueue(newProcess);
      freeProcess(newProcess);
      return (status = ERR_MEMORY);
    }

  if (kernelMemoryShare(newProcess->processId, newProcess->parentProcessId,
			newArgAddress, (void **) &argSpace) < 0)
    {
      kernelMemoryRelease(stackMemoryAddr);
      removeProcessFromQueue(newProcess);
      freeProcess(newProcess);
      return (status = ERR_MEMORY);
    }

  args[0] = execImage->argc;
  args[1] = (int) newArgAddress;
	
  argv = (char **) argSpace;
  argSpace += ((execImage->argc + 1) * sizeof(char *));
  newArgAddress += ((execImage->argc + 1) * sizeof(char *));

  // Copy the args into argv
  for (count = 0; count < execImage->argc; count ++)
    {
      strcpy((argSpace + length), execImage->argv[count]);
      argv[count] = (newArgAddress + length);
      length += (strlen(execImage->argv[count]) + 1);
    }

  // argv[argc] is supposed to be a NULL pointer, according to
  // some standard or other
  argv[args[0]] = NULL;

  // Unmap the argument space from this process
  kernelPageUnmap(newProcess->parentProcessId, argSpace, argSpaceSize);

  // Make the process own its stack memory
  status = kernelMemoryChangeOwner(newProcess->parentProcessId, 
				   newProcess->processId, 1, // remap
				   stackMemoryAddr, 
				   (void **) &(newProcess->userStack));
  if (status < 0)
    {
      // Couldn't make the process own its memory
      kernelMemoryRelease(stackMemoryAddr);
      removeProcessFromQueue(newProcess);
      freeProcess(newProcess);
      return (status);
    }

  // Make the topmost page of the user stack privileged, so that we have
  // have a 'guard page' that produces a page fault in case of (userspace)
  // stack overflow
  kernelPageSetAttrs(newProcess->processId, 0, PAGEFLAG_USER,
		     newProcess->userStack, MEMORY_PAGE_SIZE);

  if (newProcess->processorPrivilege != PRIVILEGE_SUPERVISOR)
    {
      // Get the new virtual address of supervisor stack
      newProcess->superStack = (newProcess->userStack + DEFAULT_STACK_SIZE);

      // Make the entire supervisor stack privileged
      kernelPageSetAttrs(newProcess->processId, 0, PAGEFLAG_USER,
			 newProcess->superStack, newProcess->superStackSize);
    }

  // Create the TSS (Task State Segment) for this process.
  status = createTaskStateSegment(newProcess);
  if (status < 0)
    {
      // Not able to create the TSS
      freeProcess(newProcess);
      return (status);
    }

  // Adjust the stack pointer to account for the arguments that we copied to
  // the process' stack
  newProcess->taskStateSegment.ESP -= sizeof(int);

  // Set the EIP to the entry point
  newProcess->taskStateSegment.EIP = (unsigned) execImage->entryPoint;

  newProcess->justCreated = 1;

  kernelPerformanceCounterIncI(pcProcessesCreated, 1);

  // Return the processId on success.
  return (status = newProcess->processId);
}


static int deleteProcess(kernelProcess *killProcess)
{
  // Does all the work of actually destroyng a process when there's really
  // no more use for it.  This occurs after all descendent threads have
  // terminated, for example.

  int status = 0;

  // Processes cannot delete themselves
  if (killProcess == kernelCurrentProcess)
    {
      kernelError(kernel_error, "Process %d cannot delete itself",
		  killProcess->processId);
      return (status = ERR_INVALID);
    }

  // We need to deallocate the TSS descriptor allocated to the process, if
  // it has one
  if (killProcess->tssSelector)
    {
      status = kernelDescriptorRelease(killProcess->tssSelector);
      // If this was unsuccessful, we don't want to continue and "lose" 
      // the descriptor
      if (status < 0)
	{
	  kernelError(kernel_error, "Can't release TSS");
	  return (status);
	}
    }

  // Try to close all network connections owned by this process
  status = kernelNetworkCloseAll(killProcess->processId);
  if (status < 0)
    kernelError(kernel_warn, "Can't release network connections");

  // If the process has a signal stream, destroy it
  if (killProcess->signalStream.buffer)
    kernelStreamDestroy(&(killProcess->signalStream));

  // Deallocate all memory owned by this process
  status = kernelMemoryReleaseAllByProcId(killProcess->processId);
  // Likewise, if this deallocation was unsuccessful, we don't want to
  // deallocate the process structure.  If we did, the memory would become
  // "lost".
  if (status < 0)
    {
      kernelError(kernel_error, "Can't release process memory");
      return (status);
    }

  // Delete the page table we created for this process
  status = kernelPageDeleteDirectory(killProcess->processId);
  // If this deletion was unsuccessful, we don't want to deallocate the 
  // process structure.  If we did, the page directory would become "lost".
  if (status < 0)
    {
      kernelError(kernel_error, "Can't release page directory");
      return (status);
    }

  if (fpuProcess == killProcess)
    fpuProcess = NULL;

  // Remove the process from the multitasker's process queue.
  status = removeProcessFromQueue(killProcess);
  if (status < 0)
    {
      // Not able to remove the process
      kernelError(kernel_error, "Can't dequeue process");
      return (status);
    }

  // Finally, release the process structure
  status = freeProcess(killProcess);
  if (status < 0)
    {
      kernelError(kernel_error, "Can't release process structure");
      return (status);
    }

  return (status = 0);
}


__attribute__((noreturn))
static void exceptionHandler(void)
{
  // This code is the general exception handler.  Before multitasking starts,
  // it will be called as a function in the exception context (the context
  // of the process that experienced the exception).  After multitasking
  // starts, it is a separate kernel thread that sleeps until woken up.

  char message[256];
  char *symbolName = NULL;
  int interrupt = 0;
  int count;
  
  extern kernelSymbol *kernelSymbols;
  extern int kernelNumberSymbols;

  while (1)
    {
      // We got an exception.

      if (multitaskingEnabled)
	{
	  if (kernelCurrentProcess == NULL)
	    // We have to make an error here.  We can't return to the program
	    // that caused the exception, and we can't tell the multitasker
	    // to kill it.  We'd better make a kernel panic.
	    kernelPanic("Exception handler unable to determine current "
			"process");
	  else
	    kernelCurrentProcess->state = proc_stopped;
	}

      // If the fault occurred while we were processing an interrupt,
      // we should tell the PIC that the interrupt service routine is
      // finished.  It's not really fair to kill a process because an
      // interrupt handler is screwy, but that's what we have to do for
      // the time being.
      if (kernelProcessingInterrupt)
	kernelPicEndOfInterrupt(0xFF);

      else if (!multitaskingEnabled || (kernelCurrentProcess == kernelProc))
	sprintf(message, "The kernel has experienced %s %s exception",
		exceptionVector[processingException].a,
		exceptionVector[processingException].name);
      else
	sprintf(message, "Process \"%s\" caused %s %s exception",
		kernelCurrentProcess->processName,
		exceptionVector[processingException].a,
		exceptionVector[processingException].name);

      if (exceptionAddress >= KERNEL_VIRTUAL_ADDRESS)
	{
	  if (kernelSymbols)
	    {
	      // Find roughly the kernel function where the exception
	      // happened
	      for (count = 0; count < kernelNumberSymbols; count ++)
		{
		  if ((exceptionAddress >= kernelSymbols[count].address) &&
		      (exceptionAddress <
		       kernelSymbols[count + 1].address))
		    {
		      symbolName = kernelSymbols[count].symbol;
		      break;
		    }
		}
	    }

	  if (symbolName)
	    sprintf((message + strlen(message)), " in function %s (%08x)",
		    symbolName, exceptionAddress);
	  else
	    sprintf((message + strlen(message)), " at kernel address %08x",
		    exceptionAddress);
	}
      else
	sprintf((message + strlen(message)), " at application address "
		"%08x", exceptionAddress);

      if (kernelProcessingInterrupt)
	{
	  interrupt = kernelPicGetActive();
	  if (interrupt >= 0)
	    sprintf((message + strlen(message)), " while processing interrupt "
		    "%d", interrupt);
	}

      if (!multitaskingEnabled || (kernelCurrentProcess == kernelProc))
	{
	// If it's the kernel, we're finished
	kernelPanic(message);
	}
      else
	{
	  kernelError(kernel_error, message);

	  // If we're in graphics mode, make an error dialog (but don't get
	  // into an endless loop if the crashed process was an error dialog
	  // thread itself).
	  if (kernelGraphicsAreEnabled() &&
	      strcmp((char *) kernelCurrentProcess->processName,
		     ERRORDIALOG_THREADNAME))
	    kernelErrorDialog("Application Exception", message);
	}

      /*
      // If the process was in kernel code do a stack trace
      if (!kernelProcessingInterrupt && (address >= KERNEL_VIRTUAL_ADDRESS))
	kernelStackTrace((kernelCurrentProcess->userStack +
			  ((void *) kernelCurrentProcess
			   ->taskStateSegment.ESP -
			   kernelCurrentProcess->userStack)),
			 (kernelCurrentProcess->userStack +
			  kernelCurrentProcess->userStackSize -
			  sizeof(void *)));
      */

      // The scheduler may now dismantle the process
      kernelCurrentProcess->state = proc_finished;

      kernelProcessingInterrupt = 0;
      processingException = 0;
      exceptionAddress = 0;

      // Yield the timeslice back to the scheduler.  The scheduler will take
      // care of dismantling the process
      kernelMultitaskerYield();
    }
}


static int spawnExceptionThread(void)
{
  // This function will initialize the kernel's exception handler thread.  
  // It should be called after multitasking has been initialized.  

  int status = 0;
  int procId = 0;

  // Create the kernel's exception handler thread.
  procId =
    kernelMultitaskerSpawn(&exceptionHandler, "exception thread", 0, NULL);
  if (procId < 0)
    return (status = procId);

  exceptionProc = getProcessById(procId);
  if (exceptionProc == NULL)
    return (status = ERR_NOCREATE);

  // Set the process state to sleep
  exceptionProc->state = proc_sleeping;

  status = kernelDescriptorSet(
	       exceptionProc->tssSelector, // TSS selector
	       &(exceptionProc->taskStateSegment), // Starts at...
	       sizeof(kernelTSS),      // Maximum size of a TSS selector
	       1,                      // Present in memory
	       PRIVILEGE_SUPERVISOR,   // Highest privilege level
	       0,                      // TSS's are system segs
	       0x9,                    // TSS, 32-bit, non-busy
	       0,                      // 0 for SMALL size granularity
	       0);                     // Must be 0 in TSS
  if (status < 0)
    // Something went wrong
    return (status);

  // Interrupts should always be disabled for this task 
  exceptionProc->taskStateSegment.EFLAGS = 0x00000002;

  return (status = 0);
}

#include "kernelMultitasker_IdleThread.c"

static int markTaskBusy(int tssSelector, int busy)
{
  // This function gets the requested TSS selector from the GDT and
  // marks it as busy/not busy.  Returns negative on error.
  
  int status = 0;
  kernelDescriptor descriptor;
  
  // Initialize our empty descriptor
  kernelMemClear(&descriptor, sizeof(kernelDescriptor));

  // Fill out our descriptor with data from the "official" one that 
  // corresponds to the selector we were given
  status = kernelDescriptorGet(tssSelector, &descriptor);
  if (status < 0)
    return (status);

  // Ok, now we can change the selector in the table
  if (busy)
    descriptor.attributes1 |= 0x00000002;
  else
    descriptor.attributes1 &= ~0x00000002;
    
  // Re-set the descriptor in the GDT
  status =  kernelDescriptorSetUnformatted(tssSelector, 
   descriptor.segSizeByte1, descriptor.segSizeByte2, descriptor.baseAddress1,
   descriptor.baseAddress2, descriptor.baseAddress3, descriptor.attributes1, 
   descriptor.attributes2, descriptor.baseAddress4);
  if (status < 0)
    return (status);

  // Return success
  return (status = 0);
}

/****************************************************************************/

#include "kernelMultitasker_Dispatcher.c"

/****************************************************************************/

static int createKernelProcess(void)
{
  // This function will create the kernel process at initialization time.
  // Returns 0 on success, negative otherwise.

  int status = 0;
  int kernelProcId = 0;
  processImage kernImage = {
    (void *) KERNEL_VIRTUAL_ADDRESS,
    kernelMain, 
    NULL, 0xFFFFFFFF,
    NULL, 0xFFFFFFFF,
    0xFFFFFFFF,
    "", 0, { NULL }
  };

  // The kernel process is its own parent, of course, and it is owned 
  // by "admin".  We create no page table, and there are no arguments.
  kernelProcId = 
    createNewProcess("kernel process", 1, PRIVILEGE_SUPERVISOR, &kernImage, 0);
  if (kernelProcId < 0)
    // Damn.  Not able to create the kernel process
    return (kernelProcId);

  // Get the pointer to the kernel's process
  kernelProc = getProcessById(kernelProcId);

  // Make sure it's not NULL
  if (kernelProc == NULL)
    // Can't access the kernel process
    return (status = ERR_NOSUCHPROCESS);

  // Interrupts are initially disabled for the kernel
  kernelProc->taskStateSegment.EFLAGS = 0x00000002;

  // Set the current process to initially be the kernel process
  kernelCurrentProcess = kernelProc;

  // Deallocate the stack that was allocated, since the kernel already
  // has one set up by the OS loader.
  kernelMemoryRelease(kernelProc->userStack);

  // Create the kernel process' environment
  status = kernelEnvironmentCreate(KERNELPROCID, (variableList *)
				   &(kernelProc->environment), NULL);
  if (status < 0)
    // Couldn't create an environment structure for this process
    return (status);

  // Make the kernel's text streams be the console streams
  kernelProc->textInputStream = kernelTextGetConsoleInput();
  kernelProc->textInputStream->ownerPid = KERNELPROCID;
  kernelProc->textOutputStream = kernelTextGetConsoleOutput();

  // Make the kernel process runnable
  kernelProc->state = proc_ready;

  // Return success
  return (status = 0);
}


static void incrementDescendents(kernelProcess *theProcess)
{
  // This will walk up a chain of dependent child threads, incrementing
  // the descendent count of each parent
  
  kernelProcess *parentProcess = NULL;

  if (theProcess->processId == KERNELPROCID)
    // The kernel is its own parent
    return;

  parentProcess = getProcessById(theProcess->parentProcessId);
  if (parentProcess == NULL)
    // No worries.  Probably not a problem
    return;

  parentProcess->descendentThreads++;

  if (parentProcess->type == proc_thread)
    // Do a recursion to walk up the chain
    incrementDescendents(parentProcess);

  // Done
  return;
}


static void decrementDescendents(kernelProcess *theProcess)
{
  // This will walk up a chain of dependent child threads, decrementing
  // the descendent count of each parent
  
  kernelProcess *parentProcess = NULL;

  if (theProcess->processId == KERNELPROCID)
    // The kernel is its own parent
    return;

  parentProcess = getProcessById(theProcess->parentProcessId);
  if (parentProcess == NULL)
    // No worries.  Probably not a problem
    return;

  parentProcess->descendentThreads--;

  if (parentProcess->type == proc_thread)
    // Do a recursion to walk up the chain
    decrementDescendents(parentProcess);

  // Done
  return;
}


static void kernelProcess2Process(kernelProcess *kernProcess,
				  process *userProcess)
{
  // Given a kernel-space process structure, create the corresponding
  // user-space version.
  
  strncpy(userProcess->processName, (char *) kernProcess->processName,
	  MAX_PROCNAME_LENGTH);
  userProcess->userId = kernProcess->userId;
  userProcess->processId = kernProcess->processId;
  userProcess->type = kernProcess->type;
  userProcess->priority = kernProcess->priority;
  userProcess->privilege = kernProcess->privilege;
  userProcess->parentProcessId = kernProcess->parentProcessId;
  userProcess->descendentThreads = kernProcess->descendentThreads;
  userProcess->cpuPercent = kernProcess->cpuPercent;
  userProcess->state = kernProcess->state;
}


static int fpuExceptionHandler(void)
{
	// This function gets called when a EXCEPTION_DEVNOTAVAIL (7) exception
	// occurs.  It can happen under two circumstances:
	// CR0[EM] is set: No FPU is present.  We can implement emulation here
	//     later in this case, if we want.
	// CR0[TS] and CR0[MP] are set: A task switch has occurred since the
	//     last FP operation, and we need to restore the state.

	int status = 0;
	unsigned short fpuReg = 0;

	kernelDebug(debug_multitasker, "FPU exception start");

	kernelPerformanceCounterIncI(pcFpuExceptions, 1);

	kernelProcessorClearTaskSwitched();

	if (fpuProcess && (fpuProcess == kernelCurrentProcess))
	{
		// This was the last process to use the FPU.  The state should be the
		// same as it was, so there's nothing to do.
		kernelDebug(debug_multitasker, "FPU exception end: nothing to do");
		return (status = 0);
	}

	kernelProcessorGetFpuStatus(fpuReg);
	while (fpuReg & 0x8000)
	{
		kernelDebugError("FPU is busy");
		kernelProcessorGetFpuStatus(fpuReg);
	}

	// Save the FPU state for the previous process
	if (fpuProcess)
	{
		// Save FPU state
		kernelDebug(debug_multitasker, "Switch FPU ownership from %s to %s", fpuProcess->name, kernelCurrentProcess->name);
		kernelDebug(debug_multitasker, "Save FPU state for %s", fpuProcess->name);
		kernelProcessorFpuStateSave(fpuProcess->fpuState[0]);
		fpuProcess->fpuStateSaved = 1;
		kernelPerformanceCounterIncI(pcFpuContextSaved, 1);
	}

	if (kernelCurrentProcess->fpuStateSaved)
	{
		// Restore the FPU state
		kernelDebug(debug_multitasker, "Restore FPU state for %s", kernelCurrentProcess->name);
		kernelProcessorFpuStateRestore(kernelCurrentProcess->fpuState[0]);
		kernelPerformanceCounterIncI(pcFpuContextRestored, 1);
	}
	else
	{
		// No saved state for the FPU.  Initialize it.
		kernelDebug(debug_multitasker, "Initialize FPU for %s", kernelCurrentProcess->name);
		kernelProcessorFpuInit();
		kernelProcessorGetFpuControl(fpuReg);
		// Mask FPU exceptions.
		fpuReg |= 0x3F;
		kernelProcessorSetFpuControl(fpuReg);
	}

	kernelCurrentProcess->fpuStateSaved = 0;

	kernelProcessorFpuClearEx();

	fpuProcess = kernelCurrentProcess;

	kernelDebug(debug_multitasker, "FPU exception end");
	return (status = 0);

}


/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
//
//  Below here, the functions are exported for external use
//
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////


int kernelMultitaskerInitialize(void)
{
  // This function intializes the kernel's multitasker.

  int status = 0;
  unsigned cr0 = 0;
  
  // Make sure multitasking is NOT enabled already
  if (multitaskingEnabled)
    return (status = ERR_ALREADY);

  // Now we must initialize the process queue
  kernelMemClear(processQueue, (MAX_PROCESSES * sizeof(kernelProcess *)));
  numQueued = 0;

  // Initialize the CPU for floating point operation.  We set
  // CR0[EM]=0 (no emulation)
  // CR0[MP]=1 (math present)
  // CR0[NE]=1 (floating point errors cause exceptions)
  kernelProcessorGetCR0(cr0);
  cr0 = ((cr0 & ~0x04UL) | 0x22);
  kernelProcessorSetCR0(cr0);

  // We need to create the kernel's own process.
  status = createKernelProcess();
  // Make sure it was successful
  if (status < 0)
    return (status);

  // Now start the scheduler
  status = schedulerInitialize();
  if (status < 0)
    // The scheduler couldn't start
    return (status);

  // Create an "idle" thread to consume all unused cycles
  status = spawnIdleThread();
  // Make sure it was successful
  if (status < 0)
    return (status);

  // Set up any specific exception handlers.
  exceptionVector[EXCEPTION_DEVNOTAVAIL].handler = fpuExceptionHandler;

  // Start the exception handler thread.
  status = spawnExceptionThread();
  // Make sure it was successful
  if (status < 0)
    return (status);

  // Log a boot message
  kernelLog("Multitasking started");

  pcDispatchCalls           = kernelPerformanceCounterRegister("multitasker.dispatch_calls");
  pcDispatchYieldCalls      = kernelPerformanceCounterRegister("multitasker.dispatch_yield_calls");
  pcDispatchAsyncCalls      = kernelPerformanceCounterRegister("multitasker.dispatch_async_calls");
  pcDispatchRescheduleCalls = kernelPerformanceCounterRegister("multitasker.dispatch_reschedule_calls");
  pcContextSwitches         = kernelPerformanceCounterRegister("multitasker.context_switches");
  pcWaitCalls               = kernelPerformanceCounterRegister("multitasker.wait_calls");
  pcBlockCalls              = kernelPerformanceCounterRegister("multitasker.block_calls");
  pcProcessesCreated        = kernelPerformanceCounterRegister("multitasker.processes_created");
  pcFpuExceptions           = kernelPerformanceCounterRegister("multitasker.fpu_exceptions");
  pcFpuContextSaved         = kernelPerformanceCounterRegister("multitasker.fpu_context_saved");
  pcFpuContextRestored      = kernelPerformanceCounterRegister("multitasker.fpu_context_restored");

  // Return success
  return (status = 0);
}


int kernelMultitaskerShutdown(int nice)
{
  // This function will shut down the multitasker and halt the scheduler, 
  // returning exclusive control to the kernel process.  If the nice
  // argument is non-zero, this function will do a nice orderly shutdown,
  // killing all the running processes gracefully.  If it is zero, the
  // resources allocated to the processes will never be freed, and the
  // multitasker will just stop.  Returns 0 on success, negative otherwise.
 
  int status = 0;

  // Make sure multitasking has been enabled
  if (!multitaskingEnabled)
    // We can't yield if we're not multitasking yet
    return (status = ERR_NOTINITIALIZED);

  // If we are doing a "nice" shutdown, we will kill all the running
  // processes (except the kernel and scheduler) gracefully.
  if (nice)
    kernelMultitaskerKillAll();

  // Set the schedulerStop flag to stop the scheduler
  schedulerStop = 1;

  // Yield control back to the scheduler, so that it can stop
  kernelMultitaskerYield();

  // Make note that the multitasker has been disabled
  multitaskingEnabled = 0;

  // Deallocate the stack used by the scheduler
  kernelMemoryRelease(schedulerProc->userStack);

  // Print a message
  kernelLog("Multitasking stopped");
  
  return (status = 0);
}

/*
void kernelException(int num, unsigned address)
{
	// If we are already processing one, then it's a double-fault and we are
	// totally finished
	if (processingException)
	{
		kernelPanic("Double-fault (%s) while processing %s %s exception",
			exceptionVector[num].name,
			exceptionVector[processingException].a,
			exceptionVector[processingException].name);
	}

	processingException = num;
	exceptionAddress = address;

	// If there's a handler for this exception type, call it
	if (exceptionVector[processingException].handler
	&& (exceptionVector[processingException].handler() >= 0))
	{
		// The exception was handled.  Return to the caller.
		processingException = 0;
		exceptionAddress = 0;
		return;
	}

	// If multitasking is enabled, switch to the exception thread.  Otherwise
	// just call the exception handler as a function.
	if (multitaskingEnabled)
		kernelProcessorFarCall(exceptionProc->tssSelector);
	else
		exceptionHandler();

	// If the exception is handled, then this code is reached we return.
	return;
}
*/

void kernelException(int num, void* contex)
{
	// If we are already processing one, then it's a double-fault and we are
	// totally finished
	if (processingException)
	{
		kernelPanic("Double-fault (%s) while processing %s %s exception",
			exceptionVector[num].name,
			exceptionVector[processingException].a,
			exceptionVector[processingException].name);
	}

	processingException = num;
	exceptionAddress = ((kernelInterruptState*)contex)->eip;

	// If there's a handler for this exception type, call it
	if (exceptionVector[processingException].handler
	&& (exceptionVector[processingException].handler() >= 0))
	{
		// The exception was handled.  Return to the caller.
		processingException = 0;
		exceptionAddress = 0;
		return;
	}

	// If multitasking is enabled, switch to the exception thread.  Otherwise
	// just call the exception handler as a function.
	if (multitaskingEnabled)
		kernelProcessorFarCall(exceptionProc->tssSelector);
	else
		exceptionHandler();

	// If the exception is handled, then this code is reached we return.
	return;
}

void kernelMultitaskerDumpProcessList(void)
{
  // This routine is used to dump an internal listing of the current
  // process to the output.

  kernelTextOutputStream *currentOutput = NULL;
  kernelProcess *tmpProcess = NULL;
  char buffer[1024];
  int count;

  // Make sure multitasking has been enabled
  if (!multitaskingEnabled)
    return;

  // Get the current output stream
  currentOutput = kernelTextGetCurrentOutput();

  if (numQueued > 0)
    {
      kernelTextStreamPrintLine(currentOutput, "Process list:");

      for (count = 0; count < numQueued; count ++)
	{
	  tmpProcess = processQueue[count];
	  
	  sprintf(buffer, "\"%s\"  PID=%d UID=%d priority=%d "
		  "priv=%d parent=%d\n        %d%% CPU State=",
		  (char *) tmpProcess->processName,
		  tmpProcess->processId, tmpProcess->userId,
		  tmpProcess->priority, tmpProcess->privilege,
		  tmpProcess->parentProcessId, tmpProcess->cpuPercent);
	  // Get the state
	  switch(tmpProcess->state)
	    {
	    case proc_running:
	      strcat(buffer, "running");
	      break;
	    case proc_ready:
	      strcat(buffer, "ready");
	      break;
	    case proc_waiting:
	      strcat(buffer, "waiting");
	      break;
	    case proc_sleeping:
	      strcat(buffer, "sleeping");
	      break;
	    case proc_stopped:
	      strcat(buffer, "stopped");
	      break;
	    case proc_finished:
	      strcat(buffer, "finished");
	      break;
	    case proc_zombie:
	      strcat(buffer, "zombie");
	      break;
	    default:
	      strcat(buffer, "unknown");
	      break;
	    }
	  kernelTextStreamPrintLine(currentOutput, buffer);
	}
    }
  else
    // This doesn't seem at all likely.
    kernelTextStreamPrintLine(currentOutput, "No processes remaining");

  kernelTextStreamNewline(currentOutput);
  return;
}


int kernelMultitaskerCreateProcess(const char *name, int privilege,
				   processImage *execImage)
{
	// This function is called to set up an (initially) single-threaded
	// process in the multitasker.  This is the routine used by external
	// sources -- the loader for example -- to define new processes.  This 
	// new process thread we're creating will have its state set to "stopped" 
	// after this call.  The caller should use the 
	// kernelMultitaskerChangeThreadState routine to start the new thread.  
	// This function returns the processId of the new process on success, 
	// negative otherwise.

	int status = 0;
	int processId = 0;
	kernelProcess *newProcess = NULL;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	// Make sure the parameters are valid
	if ((name == NULL) || (execImage == NULL))
		return (status = ERR_NULLPARAMETER);

	// Make sure that an unprivileged process is not trying to create a
	// privileged one
	if ((kernelCurrentProcess->privilege == PRIVILEGE_USER) &&
	    (privilege == PRIVILEGE_SUPERVISOR))
	{
		kernelError(kernel_error, "An unprivileged process cannot create a privileged process");
		return (status == ERR_PERMISSION);
	}

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// Create the new process
	processId = createNewProcess(name, PRIORITY_DEFAULT, privilege, execImage, 1); // create page directory

	// Get the pointer to the new process from its process Id
	newProcess = getProcessById(processId);
	if (newProcess == NULL)
	{
		// We couldn't get access to the new process
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOCREATE);
	}

	// Create the process' environment
	status = kernelEnvironmentCreate(newProcess->processId, (variableList *)
	                                 &(newProcess->environment), (variableList *)
	                                 &(kernelCurrentProcess->environment));
	if (status < 0)
	{
		/* FIXME: Do we need to delete the process here? */
		newProcess->state = proc_finished;

		// Couldn't create an environment structure for this process
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status);
	}

	// Don't assign input or output streams to this process.  There are
	// multiple possibilities here, and the caller will have to either block
	// (which takes care of such things) or sort it out for themselves.

	RELEASE_LOCK_PROCESS_QUEUE();

	// Return whatever was returned by the previous call
	return (processId);
}


int kernelMultitaskerSpawn(void *startAddress, const char *name, int argc,
			   void *argv[])
{
	// This function is used to spawn a new thread from the current
	// process.  The function needs to be told the starting address of
	// the code to execute, and an optional argument to pass to the 
	// spawned function.  It returns the new process Id on success,
	// negative otherwise.

	int status = 0;
	int processId = 0;
	kernelProcess *newProcess = NULL;
	processImage execImage;
	int count;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	// Check params
	if ((startAddress == NULL) || (name == NULL))
		return (status = ERR_NULLPARAMETER);

	// If the number of arguments is not zero, make sure the arguments
	// pointer is not NULL
	if (argc && !argv)
		return (status = ERR_NULLPARAMETER);

	// Make sure the current process isn't NULL
	if (kernelCurrentProcess == NULL)
		return (status = ERR_NOSUCHPROCESS);

	kernelMemClear(&execImage, sizeof(processImage));
	execImage.virtualAddress = startAddress;
	execImage.entryPoint = startAddress;
	execImage.code = NULL;
	execImage.codeSize = 0;
	execImage.data = NULL;
	execImage.dataSize = 0;
	execImage.imageSize = 0;

	// Set up arguments
	execImage.argc = (argc + 1);
	execImage.argv[0] = (char *) name;
	for (count = 0; count < argc; count ++)
		execImage.argv[count + 1] = argv[count];

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// OK, now we should create the new process
	processId = createNewProcess(name, kernelCurrentProcess->priority,
	                             kernelCurrentProcess->privilege,
	                             &execImage, 0);
	if (processId < 0)
	{
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = processId);
	}
  
	// Get the pointer to the new process from its process Id
	newProcess = getProcessById(processId);

	// Make sure it's valid
	if (newProcess == NULL)
	{
		// We couldn't get access to the new process
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOCREATE);
	}

	// Change the type to thread
	newProcess->type = proc_thread;
  
	// Increment the descendent counts 
	incrementDescendents(newProcess);

	// Since we assume that the thread is invoked as a function call, 
	// subtract additional bytes from the stack pointer to account for
	// the space where the return address would normally go.
	newProcess->taskStateSegment.ESP -= sizeof(void *);

	// Copy the environment
	newProcess->environment = kernelCurrentProcess->environment;

	// The new process should share (but not own) the same text streams as the
	// parent
	newProcess->textInputStream = kernelCurrentProcess->textInputStream;
	newProcess->textOutputStream = kernelCurrentProcess->textOutputStream;

	// Make the new thread runnable
	newProcess->state = proc_ready;

	if (kernelCurrentProcess == kernelProc)
		kernelLog("Spawned kernel thread: %s", execImage.argv[0]);

	RELEASE_LOCK_PROCESS_QUEUE();

	// Return the new process' Id.
	return (processId);
}


int kernelMultitaskerSpawnKernelThread(void *startAddress, const char *name, 
				       int argc, void *argv[])
{
	// This function is a wrapper around the regular spawn() call, which
	// causes threads to be spawned as children of the kernel, instead of
	// children of the calling process.  This is important for threads that
	// are spawned from code which belongs to the kernel.

	int status = 0;
	//int interrupts = 0;
	kernelProcess *myProcess;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// What is the current process?
	myProcess = kernelCurrentProcess;

	// Disable interrupts while we're monkeying
	//kernelProcessorSuspendInts(interrupts);

	// Change the current process to the kernel process
	kernelCurrentProcess = kernelProc;

	// Spawn

	status = kernelMultitaskerSpawn(startAddress, name, argc, argv);

	// Reset the current process
	kernelCurrentProcess = myProcess;

	// Reenable interrupts
	//kernelProcessorRestoreInts(interrupts);

	RELEASE_LOCK_PROCESS_QUEUE();

	// Done
	return (status);
}


int kernelMultitaskerGetProcess(int processId, process *userProcess)
{
	// Return the requested process.

	int status = 0;
	kernelProcess *kernProcess = NULL;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);
  
	// Check params
	if (userProcess == NULL)
		return (status = ERR_NULLPARAMETER);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// Try to match the requested process Id number with a real
	// live process structure
	kernProcess = getProcessById(processId);
	if (kernProcess == NULL)
	{
		// That means there's no such process
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHENTRY);
	}

	// Make it into a user space process
	kernelProcess2Process(kernProcess, userProcess);

	RELEASE_LOCK_PROCESS_QUEUE();

	return (status = 0);
}


int kernelMultitaskerGetProcessByName(const char *processName,
				      process *userProcess)
{
	// Return the requested process.

	int status = 0;
	kernelProcess *kernProcess = NULL;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);
  
	// Check params
	if ((processName == NULL) || (userProcess == NULL))
		return (status = ERR_NULLPARAMETER);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// Try to match the requested process Id number with a real
	// live process structure
	kernProcess = getProcessByName(processName);
	if (kernProcess == NULL)
	{
		// That means there's no such process
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHENTRY);
	}

	// Make it into a user space process
	kernelProcess2Process(kernProcess, userProcess);

	RELEASE_LOCK_PROCESS_QUEUE();

	return (status = 0);
}


int kernelMultitaskerGetProcesses(void *buffer, unsigned buffSize)
{
	// Return user-space process structures into the supplied buffer

	int status = 0;
	kernelProcess *kernProcess = NULL;
	process *userProcess = NULL;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);
  
	// Check params
	if (buffer == NULL)
		return (status = ERR_NULLPARAMETER);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	for (status = 0; status < numQueued; status ++)
	{
		kernProcess = processQueue[status];
		userProcess = (buffer + (status * sizeof(process)));
		if ((void *) userProcess >= (buffer + buffSize))
			break;

		kernelProcess2Process(kernProcess, userProcess);
	}

	RELEASE_LOCK_PROCESS_QUEUE();

	return (status);
}


int kernelMultitaskerGetCurrentProcessId(void)
{
	// This is a very simple routine that can be called by external 
	// programs to get the PID of the current running process.  Of course,
	// internal functions can perform this action very easily themselves.

	int status = 0;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		// If we're not multitasking,return the kernel's process Id
		return (status = KERNELPROCID);
  
	// Double-check the current process to make sure it's not NULL
	if (kernelCurrentProcess == NULL)
		return (status = ERR_NOSUCHPROCESS);

	// OK, we can return process Id of the currently running process
	return (status = kernelCurrentProcess->processId);
}


int kernelMultitaskerGetProcessState(int processId, processState *state)
{
	// This is a very simple routine that can be called by external 
	// programs to request the state of a "running" process.  Of course,
	// internal functions can perform this action very easily themselves.

	int status = 0;
	kernelProcess *theProcess;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	// Make sure the kernelProcessState pointer we were given is legal
	if (state == NULL)
		// Oopsie
		return (status = ERR_NULLPARAMETER);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// We need to find the process structure based on the process Id
	theProcess = getProcessById(processId);

	if (theProcess == NULL)
	{
		// The process does not exist
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHPROCESS);
	}

	// Set the state value of the process
	*state = theProcess->state;

	RELEASE_LOCK_PROCESS_QUEUE();

	return (status = 0);
}


int kernelMultitaskerSetProcessState(int processId, processState newState)
{
	// This is a very simple routine that can be called by external 
	// programs to change the state of a "running" process.  Of course,
	// internal functions can perform this action very easily themselves.

	int status = 0;
	kernelProcess *changeProcess;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// We need to find the process structure based on the process Id
	changeProcess = getProcessById(processId);

	if (changeProcess == NULL)
	{
		// The process does not exist
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHPROCESS);
	}

	// Permission check.  A privileged process can change the state of any 
	// other process, but a non-privileged process can only change the state 
	// of processes owned by the same user
	if (kernelCurrentProcess->privilege != PRIVILEGE_SUPERVISOR)
	{
		if (kernelCurrentProcess->userId != changeProcess->userId)
		{
			RELEASE_LOCK_PROCESS_QUEUE();
			return (status = ERR_PERMISSION);
		}
	}

	// Make sure the new state is a legal one
	if ((newState != proc_running) && (newState != proc_ready) && 
	    (newState != proc_waiting) && (newState != proc_sleeping) &&
	    (newState != proc_stopped) && (newState != proc_finished) &&
	    (newState != proc_zombie))
	{
		// Not a legal state value
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_INVALID);
	}

	// Set the state value of the process
	changeProcess->state = newState;

	RELEASE_LOCK_PROCESS_QUEUE();

	return (status);
}


int kernelMultitaskerProcessIsAlive(int processId)
{
	// Returns 1 if a process exists and has not finished (or been terminated)

	kernelProcess *targetProcess = NULL;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (0);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// Try to match the requested process Id number with a real
	// live process structure
	targetProcess = getProcessById(processId);

	if (targetProcess && (targetProcess->state != proc_finished) &&
	    (targetProcess->state != proc_zombie))
	{
		RELEASE_LOCK_PROCESS_QUEUE();
		return (1);
	}
	else
	{
		RELEASE_LOCK_PROCESS_QUEUE();
		return (0);
	}
}


int kernelMultitaskerGetProcessPriority(int processId)
{
	// This is a very simple routine that can be called by external 
	// programs to get the priority of a process.  Of course, internal 
	// functions can perform this action very easily themselves.

	int status = 0;
	kernelProcess *getProcess;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// We need to find the process structure based on the process Id
	getProcess = getProcessById(processId);

	if (getProcess == NULL)
	{
		// The process does not exist
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHPROCESS);
	}

	// No permission check necessary here

	status = getProcess->priority;

	RELEASE_LOCK_PROCESS_QUEUE();

	// Return the privilege value of the process
	return (status);
}


int kernelMultitaskerSetProcessPriority(int processId, int newPriority)
{
	// This is a very simple routine that can be called by external 
	// programs to change the priority of a process.  Of course, internal
	// functions can perform this action very easily themselves.

	int status = 0;
	kernelProcess *changeProcess;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	// Make sure the new priority is a legal one
	if ((newPriority < 0) || (newPriority >= (PRIORITY_LEVELS)))
		// Not a legal priority value
		return (status = ERR_INVALID);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// We need to find the process structure based on the process Id
	changeProcess = getProcessById(processId);

	if (changeProcess == NULL)
	{
		// The process does not exist
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHPROCESS);
	}

	// Permission check.  A privileged process can set the priority of any 
	// other process, but a non-privileged process can only change the 
	// priority of processes owned by the same user.  Additionally, a 
	// non-privileged process can only set the new priority to a value equal 
	// to or lower than its own priority.
	if (kernelCurrentProcess->privilege != PRIVILEGE_SUPERVISOR)
	{
		if ((kernelCurrentProcess->userId != changeProcess->userId) ||
		    (newPriority < kernelCurrentProcess->priority))
		{
			RELEASE_LOCK_PROCESS_QUEUE();
			return (status = ERR_PERMISSION);
		}
	}

	// Set the priority value of the process
	changeProcess->priority = newPriority;

	RELEASE_LOCK_PROCESS_QUEUE();

	return (status);
}


int kernelMultitaskerGetProcessPrivilege(int processId)
{
	// This is a very simple routine that can be called by external 
	// programs to request the privilege of a "running" process.  Of course,
	// internal functions can perform this action very easily themselves.

	int status = 0;
	kernelProcess *theProcess;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// We need to find the process structure based on the process Id
	theProcess = getProcessById(processId);

	if (theProcess == NULL)
	{
		// The process does not exist
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHPROCESS);
	}

	status = theProcess->privilege;

	RELEASE_LOCK_PROCESS_QUEUE();

	// Return the nominal privilege value of the process
	return (status);
}


int kernelMultitaskerGetCurrentDirectory(char *buffer, int buffSize)
{
	// This function will fill the supplied buffer with the name of the
	// current working directory for the current process.  Returns 0 on 
	// success, negative otherwise.

	int status = 0;
	int lengthToCopy = 0;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);
  
	// Make sure the buffer we've been passed is not NULL
	if (buffer == NULL)
		return (status = ERR_NULLPARAMETER);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// Now, the number of characters we will copy is the lesser of 
	// buffSize or MAX_PATH_LENGTH
	lengthToCopy = min(buffSize, MAX_PATH_LENGTH);

	// Otay, copy the name of the current directory into the caller's buffer
	strncpy(buffer, (char *) kernelCurrentProcess->currentDirectory, lengthToCopy);

	RELEASE_LOCK_PROCESS_QUEUE();

	// Return success
	return (status = 0);
}


int kernelMultitaskerSetCurrentDirectory(const char *newDirName)
{
	// This function will change the current directory of the current
	// process.  Returns 0 on success, negative otherwise.

	int status = 0;
	kernelFileEntry *newDir = NULL;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);
  
	// Check params
	if (newDirName == NULL)
		return (status = ERR_NULLPARAMETER);

	// Call the appropriate filesystem function to find this supposed new 
	// directory.
	newDir = kernelFileLookup(newDirName);
	if (newDir == NULL)
		return (status = ERR_NOSUCHDIR);

	// Make sure the target is actually a directory
	if (newDir->type != dirT)
		return (status = ERR_NOTADIR);

	// Okay, copy the full name of the directory into the process
	kernelFileGetFullName(newDir, (char *) kernelCurrentProcess->currentDirectory);

	// Return success
	return (status = 0);
}


kernelTextInputStream *kernelMultitaskerGetTextInput(void)
{
	// This function will return the text input stream that is attached
	// to the current process

	// If multitasking hasn't yet been enabled, we can safely assume that
	// we're currently using the default console text input.
	if (!multitaskingEnabled)
		return (kernelTextGetCurrentInput());
	else
		// Ok, return the pointer
		return (kernelCurrentProcess->textInputStream);
}


int kernelMultitaskerSetTextInput(int processId,
				  kernelTextInputStream *theStream)
{
	// Change the input stream of the process

	int status = 0;
	kernelProcess *theProcess = NULL;
	int count;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	// theStream is allowed to be NULL.

	ACQUIRE_LOCK_PROCESS_QUEUE();

	theProcess = getProcessById(processId);
	if (theProcess == NULL)
	{
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHPROCESS);
	}

	theProcess->textInputStream = theStream;

	if (theStream && (theProcess->type == proc_normal))
		theStream->ownerPid = theProcess->processId;

	// Do any child threads recursively as well.
	if (theProcess->descendentThreads)
	{
		for (count = 0; count < numQueued; count ++)
		{
			if ((processQueue[count]->parentProcessId == processId) &&
			    (processQueue[count]->type == proc_thread))
			{
				status = kernelMultitaskerSetTextInput(processQueue[count]->processId, theStream);
				if (status < 0)
				{
					RELEASE_LOCK_PROCESS_QUEUE();
					return (status);
				}
			}
		}
	}

	RELEASE_LOCK_PROCESS_QUEUE();

	return (status = 0);
}


kernelTextOutputStream *kernelMultitaskerGetTextOutput(void)
{
	// This function will return the text output stream that is attached
	// to the current process

	// If multitasking hasn't yet been enabled, we can safely assume that
	// we're currently using the default console text output.
	if (!multitaskingEnabled)
		return (kernelTextGetCurrentOutput());
	else
		// Ok, return the pointer
		return (kernelCurrentProcess->textOutputStream);
}


int kernelMultitaskerSetTextOutput(int processId,
				   kernelTextOutputStream *theStream)
{
	// Change the output stream of the process

	int status = 0;
	kernelProcess *theProcess = NULL;
	int count;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	// theStream is allowed to be NULL.

	ACQUIRE_LOCK_PROCESS_QUEUE();

	theProcess = getProcessById(processId);
	if (theProcess == NULL)
	{
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHPROCESS);
	}

	theProcess->textOutputStream = theStream;

	// Do any child threads recursively as well.
	if (theProcess->descendentThreads)
	{
		for (count = 0; count < numQueued; count ++)
		{
			if ((processQueue[count]->parentProcessId == processId) &&
			    (processQueue[count]->type == proc_thread))
			{
				status = kernelMultitaskerSetTextOutput(processQueue[count]->processId, theStream);
				if (status < 0)
				{
					RELEASE_LOCK_PROCESS_QUEUE();
					return (status);
				}
			}
		}
	}

	RELEASE_LOCK_PROCESS_QUEUE();

	return (status = 0);
}


int kernelMultitaskerDuplicateIO(int firstPid, int secondPid, int clear)
{
	// Copy the input and output streams of the first process to the second
	// process.

	int status = 0;
	kernelProcess *firstProcess = NULL;
	kernelProcess *secondProcess = NULL;
	kernelTextInputStream *input = NULL;
	kernelTextOutputStream *output = NULL;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	firstProcess  = getProcessById(firstPid);
	secondProcess = getProcessById(secondPid);

	if ((firstProcess == NULL) || (secondProcess == NULL))
	{
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOSUCHPROCESS);
	}

	input  = firstProcess->textInputStream;
	output = firstProcess->textOutputStream;

	if (input)
	{
		secondProcess->textInputStream = input;
		input->ownerPid = secondPid;
      
		if (clear)
			kernelTextInputStreamRemoveAll(input);
	}

	if (output)
		secondProcess->textOutputStream = output;

	RELEASE_LOCK_PROCESS_QUEUE();

	return (status = 0);
}


int kernelMultitaskerGetProcessorTime(clock_t *clk)
{
	// Returns processor time used by a process since its start.  This
	// value is the number of timer ticks from the system timer.

	int status = 0;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		// We can't yield if we're not multitasking yet
		return (status = ERR_NOTINITIALIZED);

	if (clk == NULL)
		return (status = ERR_NULLPARAMETER);

	// Return the processor time of the current process
	*clk = kernelCurrentProcess->cpuTime;

	return (status = 0);
}

int kernelMultitaskerDispatch(int reason)
{
	// This routine will yield control from the current running thread
	// back to the scheduler.

	if (!multitaskingEnabled)
		return -1;

	if (kernelProcessingInterrupt)
		return -1;

	if (inScheduler)
		kernelPanic("Recursive dispatch call");
		//return -1;

	int oldProcessorLevel = kernelRaiseProcessorLevel(kernelProcessorLevelDispatch);

	kernelCurrentProcess->dispatchReason = reason;

	kernelLowerProcessorLevel(oldProcessorLevel);

	return 0;
}

int kernelMultitaskerYield(void)
{
	return kernelMultitaskerDispatch(DISPATCH_YIELD);
}

#include "kernelMultitasker_Boost.c"
#include "kernelMultitasker_Wait.c"
#include "kernelMultitasker_Kill.c"
#include "kernelMultitasker_Signal.c"
#include "kernelMultitasker_IOPerm.c"

kernelPageDirectory *kernelMultitaskerGetPageDir(int processId)
{
	kernelProcess *dirProcess = NULL;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (NULL);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	dirProcess = getProcessById(processId);
	if (dirProcess == NULL)
	{
		// There's no such process
		RELEASE_LOCK_PROCESS_QUEUE();
		kernelError(kernel_error, "No process %d to get page directory", processId);
		return (NULL);
	}

	kernelPageDirectory* pageDirectory = dirProcess->pageDirectory;

	RELEASE_LOCK_PROCESS_QUEUE();

	return (pageDirectory);
}
